package com.jerome.election.sync;

import com.jerome.election.constants.AppConstant;
import com.jerome.election.util.ThreadPoolFactory;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Socket Long Connection Client
 *
 * @author Jerome
 * @date 2023/9/11
 */
public class SyncClient {
    private Socket socket;
    private ExecutorService pool;
    private URL url;
    /**
     * Heartbeat timeout time
     */
    private long keepAliveTime = 15 * 1000;
    private long lastTime = System.currentTimeMillis();
    /**
     * Specific logic implementation
     */
    private SyncClientHandler handler;

    /**
     * Send syncVo
     *
     * @param syncVo Represents synchronized data.
     * @throws IOException if there is an I/O error
     */
    public void send(SyncVo syncVo) throws IOException {
        if (socket == null || socket.isClosed()) {
            reconnect();
        }
        if (socket != null && !socket.isClosed()) {
            handler.sendHandle(socket, syncVo);
        }
    }

    /**
     * Create a socket connection
     *
     * @param urlStr  The URL string
     * @param handler The handler for handling socket requests
     * @throws IOException if there is an I/O error
     */
    public SyncClient(String urlStr, SyncClientHandler handler) throws IOException, ClassNotFoundException {
        this.handler = handler;
        if (urlStr.startsWith(AppConstant.HTTP)) {
            url = new URL(urlStr);
        } else {
            url = new URL(AppConstant.HTTP_PREFIX + urlStr);
        }
        pool = new ThreadPoolExecutor(2, 2,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1), new ThreadPoolFactory("SyncClient"));
        // Object for receiving requests
        pool.submit(new ReceiveWatchDog());
        // Heartbeat request
        pool.submit(new KeepAliveWatchDog());
    }

    /**
     * Reconnect the socket connection
     *
     * @throws IOException if there is an I/O error
     */
    private synchronized void reconnect() throws IOException {
        try {
            if (url.toString().startsWith(AppConstant.HTTP_PREFIX)) {
                socket = SocketFactory.getDefault().createSocket(url.getHost(), url.getPort() == -1 ? 80 : url.getPort());
            } else {
                socket = SSLSocketFactory.getDefault().createSocket(url.getHost(), 443);
            }
            handler.initHandle(socket);
        } catch (IOException e) {
            System.out.println(String.format("%s is offline", url));
            if (socket != null) {
                socket.close();
            }
        }

    }

    /**
     * Receive response messages
     */
    private class ReceiveWatchDog implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    if (socket == null || socket.isClosed()) {
                        Thread.sleep(100);
                        continue;
                    }
                    if (socket.getInputStream().available() > 0) {
                        // Read object data
                        lastTime = System.currentTimeMillis();
                        // Execute different logic
                        handler.receiveHandle(socket);
                    } else {
                        Thread.sleep(10);
                    }
                } catch (Exception e) {
                    // End the thread in case of an error
                    System.out.println("ReceiveWatchDog: " + e.getMessage());
                }
            }
        }
    }

    /**
     * Watchdog for keeping the connection alive
     */
    private class KeepAliveWatchDog implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    if (socket == null || socket.isClosed()) {
                        reconnect();
                        Thread.sleep(keepAliveTime/2);
                        lastTime = System.currentTimeMillis();
                    } else {
                        if (System.currentTimeMillis() - lastTime > keepAliveTime) {
                            socket.close();
                            continue;
                        }
                    }
                    handler.heartbeatHandle(socket);
//                    lastTime = System.currentTimeMillis();
                    Thread.sleep(5000);
                } catch (Exception e) {
                    // End the thread in case of an error
                    System.out.println("KeepAliveWatchDog err: " + e.getMessage());
                    try {
                        if (socket!=null){
                            socket.close();
                        }
                    } catch (Exception ioException) {
                        ioException.printStackTrace();
                    }
                }
            }

        }
    }
}
